home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2006 May / PCpro_2006_05.ISO / files / mobile / fma-2.0-stable-setup.exe / {app} / source / gsm_sms.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2005-01-26  |  29.6 KB  |  904 lines

  1. unit gsm_sms;
  2.  
  3. {
  4. *******************************************************************************
  5. * Descriptions: GSM/PDU Handing Class
  6. * $Source: /cvsroot/fma/fma/gsm_sms.pas,v $
  7. * $Locker:  $
  8. *
  9. * Todo:
  10. *   - Add support for Icelandic character
  11. *   - Rename unit as 'uGsmSMS.pas' for example
  12. *
  13. * Change Log:
  14. * $Log: gsm_sms.pas,v $
  15. * Revision 1.16.6.3  2005/01/25 16:03:05  z_stoichev
  16. * Merged with 2.1 Beta 1 bugfixes
  17. *
  18. * Revision 1.16.6.2  2004/10/15 11:27:58  z_stoichev
  19. * Bugfixes
  20. *
  21. * Revision 1.16.6.1  2004/10/14 16:43:23  z_stoichev
  22. * Bugfixes
  23. *
  24. * Revision 1.16  2004/07/26 12:50:56  z_stoichev
  25. * Force UCS-2 support
  26. *
  27. * Revision 1.15  2004/07/14 09:34:50  z_stoichev
  28. * - Fixed GSM 7bit decoding end of text detection.
  29. *
  30. * Revision 1.14  2004/07/07 10:22:38  z_stoichev
  31. * Added convert handling and debug msg
  32. *
  33. * Revision 1.13  2004/03/26 18:37:39  z_stoichev
  34. * Build 0.1.0.35 RC5
  35. *
  36. * Revision 1.12  2004/03/12 16:56:10  z_stoichev
  37. * Fixed Long SMS last character deleted.
  38. *
  39. * Revision 1.11  2004/03/04 16:53:47  z_stoichev
  40. * Fixed append @ at message end.
  41. *
  42. * Revision 1.10  2004/01/27 15:52:07  z_stoichev
  43. * Fixed prefix @@@@ on long sms.
  44. * Added update refference field method.
  45. *
  46. * Revision 1.9  2004/01/23 12:50:02  z_stoichev
  47. * Bugfixes, set get PDU, change Msg Refference
  48. *
  49. * Revision 1.8  2003/11/28 09:38:07  z_stoichev
  50. * Merged with branch-release-1-1 (Fma 0.10.28c)
  51. *
  52. * Revision 1.7.2.2  2003/11/21 10:56:07  z_stoichev
  53. * Fixed msg text cut in Fma.
  54. *
  55. * Revision 1.7.2.1  2003/10/27 07:22:53  z_stoichev
  56. * Build 0.1.0 RC1 Initial Checkin.
  57. *
  58. * Revision 1.7  2003/10/24 12:22:03  z_stoichev
  59. * Fixed UCS-2 issue with german special symbols.
  60. *
  61. * Revision 1.6  2003/10/21 09:15:37  z_stoichev
  62. * Sending UTF8/UCS2 messages (cyrillic etc. support)
  63. *
  64. * Revision 1.5  2003/07/02 12:21:23  crino77
  65. * fixed some bugs with UCS2 sms
  66. *
  67. * Revision 1.4  2003/02/14 07:23:48  crino77
  68. * Add the MessageReference
  69. * Add the UDHI support and request status
  70. * Added FMessageLength:Integer; in private
  71. * Modified GetMessage to prevent some char at the end of the message ;)
  72. * Add support for Greek - Thanks to George Billios
  73. *
  74. * Revision 1.3  2003/01/30 04:15:57  warren00
  75. * Updated with header comments
  76. *
  77. *
  78. *
  79. *******************************************************************************
  80. }
  81.  
  82. interface
  83.  
  84. uses
  85.   SysUtils, DateUtils, Dialogs, StrUtils;
  86.  
  87. type
  88.   TSMS = class(TObject)
  89.   private
  90.     FIsSMSSumit: Boolean;
  91.     FValidityLen: Integer;
  92.     FSMSCLen: Integer;
  93.     FSenderLen: Integer;
  94.     FSenderPos: Integer;
  95.     FPDU: String;
  96.     FSMSDeliverStartPos: Integer;
  97.     FMessage: WideString;
  98.     FMessageRef: String;
  99.     FAddress: String;
  100.     FFlashSMS: Boolean;
  101.     FRequestReply: Boolean;
  102.     FDataCoding: Integer;
  103.     FMessageLength: Integer;
  104.     FIsUDH: Boolean;
  105.     FUDHI: String;
  106.     FStatusRequest: Boolean;
  107.     FSizeOfPDU: integer;
  108.     procedure SetPDU(const Value: String);
  109.     function GetPDU: String;
  110.     function ReverseOctets(Octets: String): String;
  111.     function DecodeNumber(raw: String): String;
  112.     function EncodeNumber(Number: String): String;
  113.     function GetMessage: WideString;
  114.     function GetAddress: String;
  115.     function GetSMSC: String;
  116.     function GetTimeStamp: TDateTime;
  117.     function Get7bit(str: String): String;
  118.     function Get8bit(str: String): String;
  119.     function GetUCS2(str: String): WideString;
  120.     function MakeCRLF(str: string): String;
  121.     procedure Set_MessageRef(const Value: String);
  122.   public
  123.     dcs: Integer;
  124.     function GetNewPDU(AMessageReference: String): String;
  125.     property RequestReply: Boolean read FRequestReply write FRequestReply;
  126.     property PDU: String read GetPDU write SetPDU;
  127.     property UDHI: String read FUDHI write FUDHI;
  128.     property MessageReference: String read FMessageRef write Set_MessageRef;
  129.     property Text: WideString read GetMessage write FMessage;
  130.     property Number: String read GetAddress write FAddress;
  131.     property SMSC: String read GetSMSC;
  132.     property FlashSMS: Boolean read FFlashSMS write FFlashSMS;
  133.     property StatusRequest: Boolean read FStatusRequest write FStatusRequest;
  134.     property IsOutgoing: Boolean read FIsSMSSumit;
  135.     property IsUDH: Boolean read FIsUDH;
  136.     property TimeStamp: TDateTime read GetTimeStamp;
  137.     property TPLength: integer read FSizeOfPDU;
  138.   end;
  139.  
  140. const
  141.   DoStrictUCScheck: boolean = True;
  142.   ForceUCSusage: boolean = False;
  143.  
  144. function CheckCodingType(str: WideString): Integer;
  145. function ConvertCharSet(inputStr: String; toGSM: Boolean): String; overload; // from GSM mode only!
  146. function ConvertCharSet(inputChr: Char; toGSM: Boolean = False): Char; overload;
  147.  
  148. { implementation found on interget, might solve chinese utf8 problems ? 
  149. function DecodeUTF8(const Value : string):string;
  150. function EncodeUTF8(const Value : string):string;
  151. }
  152.  
  153. implementation
  154.  
  155. uses Windows, Unit1;
  156.  
  157. { UTF8 }
  158.  
  159. function DecodeUTF8(const Value : string):string;
  160. var i, j : integer;
  161.     N : integer;
  162.     HugeChar : ULONG; //4 bytes
  163. begin
  164.   Result:='';
  165.   i:=1;
  166.   while i < Length(Value) do begin
  167.     if byte(Value[i]) < $80 then begin
  168.       Result:=Result+Value[i]; //no change required
  169.       i:=i+1;
  170.     end
  171.     else begin
  172.       //find out the number of bytes used for this character
  173.       N:=0;
  174.       for j:=1 to 8 do begin
  175.       //start with the highest bit and cound the bumber
  176.       //of "1" before "0"
  177.         if (byte(Value[i]) and (1 shl (8-j))) = 0 then Break;
  178.         inc(N);
  179.       end;
  180.       //ShowMessage('N:'+IntToStr(N));
  181.       HugeChar:=byte(Value[i]) and ($FF shr (N+1));
  182.       //ShowMessage('HugeChar:'+IntToStr(HugeChar));
  183.       for j:=1 to N-1 do begin
  184.         HugeChar:=(HugeChar shl 6) or byte(byte(Value[i+j]) and $3F);
  185.       end;
  186.       //ShowMessage('HugeChar:'+IntToStr(HugeChar));
  187.       Result:=Result+char(HugeChar);
  188.       i:=i+N;
  189.     end;
  190.   end;
  191. end;
  192.  
  193. //only work on bytes 0..255
  194. function EncodeUTF8(const Value : string):string;
  195. var i : integer;
  196. begin
  197.   for i:=1 to Length(Value) do begin
  198.     if byte(Value[i]) < $80 then begin
  199.       Result:=Result+Value[i]; //no change required
  200.     end
  201.     else begin
  202.       Result:=Result+char($C0{11000000} or (byte(Value[i]) shr 6))+
  203.                      char($80{10000000} or (byte(Value[i]) and $3F{111111}));
  204.     end;
  205.   end;
  206. end;
  207.  
  208. { TSMS }
  209.  
  210. function TSMS.DecodeNumber(raw: String): String;
  211. var
  212.   addrType: Integer;
  213. begin
  214.   try
  215.     addrType := StrToInt('$' + copy(Raw, 1, 2));
  216.     if ((addrType and $50) = $50) then begin
  217.       Result := Get7bit(copy(Raw, 3, length(Raw) - 2));
  218.     end
  219.     else begin
  220.       Result := ReverseOctets(copy(Raw, 3, length(Raw) - 2));
  221.       if Result[length(Result)] = 'F' then Result := copy(Result, 1, length(Result) - 1);
  222.       if ((StrToInt('$' + copy(Raw, 1, 2)) and $70) shr 4) = 1 then Result := '+' + result;
  223.     end;
  224.   except
  225.     Result := '';
  226.   end;
  227. end;
  228.  
  229. function TSMS.EncodeNumber(Number: String): String;
  230. begin
  231.   Result := '81';
  232.  
  233.   if Number[1] = '+' then begin
  234.     Result := '91'; // International Numner, ISDN/Telephone (E.164/E.163)
  235.     Number := copy(Number, 2, length(Number));
  236.   end;
  237.  
  238.   Result := IntToHex(length(Number), 2) + Result;
  239.  
  240.   if length(Number) mod 2 > 0 then Number := Number + 'F';
  241.   Result := Result + ReverseOctets(Number);
  242. end;
  243.  
  244. function TSMS.Get7bit(str: String): String;
  245. var
  246.   i, j, x: Integer;
  247.   leftover, octet: byte;
  248.   c: string[2];
  249. begin
  250.   Result := '';
  251.   x := 1;
  252.   leftover := 0;
  253.   j := Round(length(str) / 2) - 1;
  254.  
  255.   for i := 0 to j do begin
  256.     try
  257.       c := copy(str, (i*2)+1, 2);
  258.       if not (Copy(c,1,1)[1] in ['0'..'9','A'..'F']) then
  259.         break;
  260.       if (Length(c) = 2) and not (Copy(c,2,1)[1] in ['0'..'9','A'..'F']) then
  261.         Delete(c,2,1);
  262.       octet := StrToInt('$' + c);
  263.       Result := Result + chr(((octet and ($FF shr x)) shl (x - 1)) or leftover);
  264.       leftover := (octet and (not ($FF shr x))) shr (8 - x);
  265.       x := x + 1;
  266.     except
  267.     end;
  268.  
  269.     if x = 8 then begin
  270.       { do not add extra @ at the end of text, bug 849905 fixed }
  271.       if (i <> j) or (leftover <> 0) then
  272.         Result := Result + chr(leftover);
  273.       x := 1;
  274.       leftover := 0;
  275.     end;
  276.   end;
  277.  
  278.   Result := ConvertCharSet(Result, false);
  279. end;
  280.  
  281. function TSMS.Get8bit(str: String): String;
  282. var
  283.   i: Integer;
  284.   octet: Integer;
  285. begin
  286.   Result := '';
  287.  
  288.   for i := 0 to Round(length(str) / 2) - 1 do begin
  289.     octet := StrToInt('$' + copy(str, (i*2)+1, 2));
  290.     Result := Result + chr(octet);
  291.   end;
  292.  
  293.   Result := ConvertCharSet(Result, false);
  294. end;
  295.  
  296. function TSMS.GetUCS2(str: String): WideString;
  297. var
  298.   i: Integer;
  299.   octet: Integer;
  300. begin
  301.   Result := '';
  302.  
  303.   while (length(str) mod 4) <> 0 do str := str + '0';
  304.  
  305.   for i := 0 to (length(str) div 4) - 1 do begin
  306.     octet := StrToInt('$' + copy(str, (i*4)+1, 4));
  307.     Result := Result + Widechar(octet);
  308.   end;
  309. end;
  310.  
  311. function TSMS.GetMessage: WideString;
  312. var
  313.   startpos: Integer;
  314.   str, UDHnull: String;
  315.   UDHIlength, i :Integer;
  316.   function RemoveTail00(s: string): string;
  317.   var
  318.     i: integer;
  319.   begin
  320.     i := Length(s);
  321.     if i >= 2 then begin
  322.       if Copy(s,i-1,2) = '00' then
  323.         Delete(s,i-1,2);
  324.     end;
  325.     Result := s;
  326.   end;
  327. begin
  328.   try
  329.     Result := '';
  330.     UDHILength := 0;
  331.  
  332.     startpos := FSMSDeliverStartPos + FSenderLen + FValidityLen + 12;
  333.     if not FIsSMSSumit then startpos := startpos + 12;
  334.  
  335.     if FIsUDH then begin
  336.       UDHILength := StrToInt('$' + copy(FPDU, startpos + 2, 2));
  337.  
  338.       FUDHI := copy(FPDU, startpos + 2, UDHILength * 2 + 2);
  339.       //Replace UDH with NULL chars
  340.       for i:=0 to UDHILength do begin
  341.          UDHnull := UDHnull + '00';
  342.       end;
  343.       Delete(FPDU,startpos + 2,UDHILength * 2 + 2);
  344.       Insert(UDHNull,FPDU,startpos + 2);
  345.       //FPDU := AnsiReplaceStr(FPDU, FUDHI, UDHNull);
  346.     end;
  347.  
  348.     // TP-User-Data-Length. Length of message. The TP-DCS field indicated 7-bit data, so the length here is the number
  349.     // of septets. If the TP-DCS field were set to 8-bit data or Unicode, the length would be the number of octets.
  350.     FMessageLength := StrToInt('$' + copy(FPDU, startpos, 2));
  351.  
  352.     if FDataCoding = 0 then begin
  353.        str := copy(FPDU, startpos + 2, length(FPDU)); // process the rest of PDU data, will cut the message length later
  354.        Result := Get7bit(str);
  355.        // here FMessageLength contains number of septets (decoded chars)
  356.        if FIsUDH then
  357.          Result := Copy(Result, ((UDHILength div 7) + UDHILength + 2) + 1, FMessageLength)
  358.        else
  359.          Result := Copy(Result, 1, FMessageLength);
  360.     end
  361.     else if FDataCoding = 1 then begin
  362.        // here FMessageLength contains numbers of octets (encoded bytes)
  363.        str := copy(FPDU, startpos + 2, (FMessageLength)*2);
  364.        Result := Get8bit(str);
  365.        if FIsUDH then
  366.          Result := Copy(Result, ((UDHILength div 7) + UDHILength + 2) + 1, Length(Result));
  367.     end
  368.     else if FDataCoding = 2 then begin
  369.        // here FMessageLength contains numbers of octets (encoded bytes)
  370.        str := copy(FPDU, startpos + 2, (FMessageLength)*2);
  371.        Result := GetUCS2(str);
  372.        if FIsUDH then begin
  373.          i := ((UDHILength + 1) mod 4) + 2;
  374.          Result := Copy(Result, i, Length(Result));
  375.          { TODO: unicode support instead of copy? }
  376.          //Result := WideCharLenToString(@Result[i], Length(Result) - i + 1);
  377.        end;
  378.     end
  379.     else Result := '(Unsupported: Unknown coding scheme)';
  380.  
  381.     Result := MakeCRLF(Result);
  382.   except
  383.     Result := '(Decoding Error)';
  384.   end;
  385. end;
  386.  
  387. function TSMS.GetPDU: String;
  388. var
  389.   udhl: Integer;
  390.   i, j, x, head: Integer;
  391.   Octet: String;
  392.   nextChr: Byte;
  393.  
  394.   pduAddr, pduMsgL, pduMsg: String;
  395.   pduSMSC, pduFirst, pduMsgRef, pduPID, pduDCS, pduTPVP: String;
  396.   AMessage: WideString;
  397. begin
  398.   AMessage := FMessage;
  399.   udhl := 0;
  400.   try
  401.     // Convert Address (Destination No)
  402.     pduAddr := EncodeNumber(FAddress);
  403.  
  404.     pduMsg := '';
  405.     if (dcs = 0) or ((dcs = -1) and (CheckCodingType(AMessage) = 0)) then begin // 7-bit coding
  406.       // Convert Message
  407.       if FUDHI <> '' then begin
  408.          udhl := StrToInt(Copy(FUDHI,1,2));
  409.          udhl := (udhl div 7) + udhl + 2;
  410.          for i:=0 to udhl - 1 do begin
  411.             AMessage := '@' + AMessage;
  412.          end;
  413.       end;
  414.       pduMsgL := IntToHex(length(AMessage), 2); // number of septets!! IT IS NOT NUMBER OF OCTETS IN 7-bit encoding mode!!
  415.  
  416.       x := 0;
  417.       j := length(AMessage);
  418.       for i := 1 to j do begin
  419.         if x < 7 then begin
  420.           if i = j then
  421.             nextChr := 0
  422.           else
  423.             nextChr := Ord(ConvertCharSet(Char(AMessage[i+1]), True));
  424.  
  425.           Octet := IntToHex( ((nextChr and (not ($FF shl (x+1)))) shl (7-x)) or (Ord(ConvertCharSet(Char(AMessage[i]), True)) shr x) , 2);
  426.           pduMsg := pduMsg + Octet;
  427.  
  428.           x := x + 1;
  429.         end
  430.         else x := 0;
  431.       end;
  432.  
  433.       pduDCS := '00';
  434.     end
  435.     else if (dcs = 1) or ((dcs = -1) and (CheckCodingType(AMessage) = 1)) then begin // 8-bit coding
  436.       if FUDHI <> '' then begin
  437.          udhl := StrToInt(Copy(FUDHI,1,2));
  438.          udhl := (udhl div 7) + udhl + 2;
  439.          for i:=0 to udhl - 1 do begin
  440.             AMessage := '@' + AMessage;
  441.          end;
  442.       end;
  443.       for i := 1 to length(AMessage) do begin
  444.         pduMsg := pduMsg + IntToHex(ord(ConvertCharSet(Char(AMessage[i]), True)), 2);
  445.       end;
  446.       pduMsgL := IntToHex(length(pduMsg) div 2,2); // number of octets
  447.  
  448.       pduDCS := '04';
  449.     end
  450.     else begin // UCS2 Coding
  451.       if FUDHI <> '' then begin
  452.          udhl := StrToInt(Copy(FUDHI,1,2));
  453.          udhl := ((udhl + 1) mod 4) + 3;
  454.          for i:=0 to udhl - 1 do begin
  455.             AMessage := '@' + AMessage;
  456.          end;
  457.          udhl := udhl*2 + 1; // adjust udhl according to UCS2 coding 
  458.       end;
  459.       for i := 1 to length(AMessage) do begin
  460.         pduMsg := pduMsg + IntToHex(ord(AMessage[i]), 4);
  461.       end;
  462.       pduMsgL := IntToHex(length(pduMsg) div 2,2); // number of octets
  463.  
  464.       pduDCS := '08';
  465.     end;
  466.     if FFlashSMS then pduDCS[1] := '1'; // i.e. '1x' depending of code scheme selected
  467.  
  468.     if FUDHI <> '' then begin
  469.        pduMsg := Copy(pduMsg, (udhl-1) * 2 + 1, length(pduMsg));
  470.     end;
  471.  
  472.     pduSMSC :=   '00'; // No SMSC Information
  473.     pduFirst :=  '11'; // No Validity Field, SMS-Sumit, No UDH, No Status Request
  474.     head := StrToInt('$' + pduFirst);
  475.     if FStatusRequest then head := head or $20; //ADD Yes StatusRequest
  476.     if FUDHI <> '' then head := head or $40;    //ADD Yes UDH
  477.     if FRequestReply then head := head or $80;  //ADD Yes ReplyRequest
  478.  
  479.     pduFirst := IntToHex(head, 2);
  480.     pduMsgRef := '00'; // Let the phone set Msg Ref itself
  481.     if FMessageRef <> '' then pduMsgRef := FMessageRef;
  482.     pduPID := '00';
  483.  
  484.     pduTPVP := 'FF';
  485.  
  486.     Result := pduFirst + pduMsgRef + pduAddr + pduPID + pduDCS + pduTPVP + pduMsgL;
  487.     if FUDHI <> '' then begin
  488.        Result := Result + FUDHI;
  489.     end;
  490.     Result := Result + pduMsg;
  491.  
  492.     FSizeOfPDU := Length(Result) div 2;
  493.  
  494.     Result := pduSMSC + Result;
  495.   except
  496.     raise Exception.Create('Error encoding PDU');
  497.   end;
  498. end;
  499.  
  500. function TSMS.GetAddress: String;
  501. begin
  502.   Result := DecodeNumber(copy(FPDU, FSenderPos, FSenderLen + 2));
  503. end;
  504.  
  505. function TSMS.GetSMSC: String;
  506. begin
  507.   if FSMSCLen > 0 then Result := DecodeNumber(copy(FPDU, 3, FSMSCLen))
  508.   else Result := '';
  509. end;
  510.  
  511. function TSMS.GetTimeStamp: TDateTime;
  512. var
  513.   str: String;
  514.   year, month, day, hour, minute, second: Integer;
  515. begin
  516.   if FIsSMSSumit then Result := 0
  517.   else begin
  518.     str := ReverseOctets(copy(FPDU, FSMSDeliverStartPos + FSenderLen + 10, 12));
  519.  
  520.     Year :=   StrToInt(copy(str,  1, 2));
  521.     Month :=  StrToInt(copy(str,  3, 2));
  522.     Day :=    StrToInt(copy(str,  5, 2));
  523.     Hour :=   StrToInt(copy(str,  7, 2));
  524.     Minute := StrToInt(copy(str,  9, 2));
  525.     Second := StrToInt(copy(str, 11, 2));
  526.  
  527.     Result := EncodeDateTime(Year+2000, Month, Day, Hour, Minute, Second, 0);
  528.   end;
  529. end;
  530.  
  531. function TSMS.ReverseOctets(Octets: String): String;
  532. var
  533.   i: Integer;
  534.   buffer: char;
  535. begin
  536.   i := 1;
  537.   while i < length(Octets) do begin
  538.     buffer := Octets[i];
  539.     Octets[i] := Octets[i+1];
  540.     Octets[i+1] := buffer;
  541.     i := i + 2;
  542.   end;
  543.  
  544.   result := Octets;
  545. end;
  546.  
  547. procedure TSMS.SetPDU(const Value: String);
  548. var
  549.   PDUType, TPVPF: Byte;
  550.   TPDCS: Integer;
  551.   Offset: Integer;
  552. begin
  553.   {
  554.   The following example shows how to send the message "hellohello" in the PDU mode from a Nokia 6110.
  555.  
  556.   AT+CMGF=0    //Set PDU mode
  557.   AT+CSMS=0    //Check if modem supports SMS commands
  558.   AT+CMGS=23  //Send message, 23 octets (excluding the two initial zeros)
  559.   >0011000B916407281553F80000AA0AE8329BFD4697D9EC37<ctrl-z>
  560.  
  561.   There are 23 octets in this message (46 'characters'). The first octet ("00") doesn't count, it is only an indicator of the length of
  562.   the SMSC information supplied (0). The PDU string consists of the following:
  563.  
  564.   Octet(s)            Description
  565.   00                  Length of SMSC information. Here the length is 0, which means that the SMSC stored in the phone should be used.
  566.                       Note: This octet is optional. On some phones this octet should be omitted! (Using the SMSC stored in phone is thus implicit)
  567.   11                  First octet of the SMS-SUBMIT message.
  568.   00                  TP-Message-Reference. The "00" value here lets the phone set the message reference number itself.
  569.   0B                  Address-Length. Length of phone number (11)
  570.   91                  Type-of-Address. (91 indicates international format of the phone number).
  571.   6407281553F8        The phone number in semi octets (46708251358). The length of the phone number is odd (11), therefore a trailing
  572.                       F has been added, as if the phone number were "46708251358F". Using the unknown format (i.e. the Type-of-Address
  573.                       81 instead of 91) would yield the phone number octet sequence 7080523185 (0708251358). Note that this has the
  574.                       length 10 (A), which is even.
  575.   00                  TP-PID. Protocol identifier
  576.   00                  TP-DCS. Data coding scheme.This message is coded according to the 7bit default alphabet. Having "04" instead of
  577.                       "00" here, would indicate that the TP-User-Data field of this message should be interpreted as 8bit rather than
  578.                       7bit (used in e.g. smart messaging, OTA provisioning etc).
  579.   AA                  TP-Validity-Period. "AA" means 4 days. Note: This octet is optional, see bits 4 and 3 of the first octet
  580.   0A                  TP-User-Data-Length. Length of message. The TP-DCS field indicated 7-bit data, so the length here is the number of
  581.                       septets (10). If the TP-DCS field were set to 8-bit data or Unicode, the length would be the number of octets.
  582.   E8329BFD4697D9EC37  TP-User-Data. These octets represent the message "hellohello". How to do the transformation from 7bit septets into
  583.                       octets is shown here
  584.   }
  585.   FPDU := Value;
  586.  
  587.   // Check if PDU contain SMSC information
  588.   try
  589.     FSMSCLen := StrToInt('$' + copy(FPDU, 1, 2)) * 2; // length in octets * 2 = number of chars
  590.   except
  591.     FSMSCLen := 0;
  592.     Form1.Debug('PDU ERROR (SMSCLen): '+Value);
  593.   end;
  594.   FSizeOfPDU := (Length(FPDU) - FSMSCLen) div 2 - 1; // number of chars - FSMSCLen's 2 chars
  595.  
  596.   FSMSDeliverStartPos := 3; // char number, first 2 represent FSMSCLen octet
  597.   if FSMSCLen > 0 then FSMSDeliverStartPos := FSMSDeliverStartPos + FSMSCLen;
  598.  
  599.   // Check if SMS-Sumit or SMS-Deliver
  600.   try
  601.     {
  602.     First octet of the SMS-DELIVER PDU
  603.     The first octet of the SMS-DELIVER PDU has the following layout:
  604.  
  605.     Bit no  7        6        5        4        3        2       1       0
  606.     Name    TP-RP    TP-UDHI  TP-SRI   (unused) (unused) TP-MMS  TP-MTI  TP-MTI
  607.  
  608.     Name    Meaning
  609.     TP-RP   Reply path. Parameter indicating that reply path exists.
  610.     TP-UDHI User data header indicator. This bit is set to 1 if the User Data field starts with a header
  611.     TP-SRI  Status report indication. This bit is set to 1 if a status report is going to be returned to the SME
  612.     TP-MMS  More messages to send. This bit is set to 0 if there are more messages to send
  613.     TP-MTI  Message type indicator. Bits no 1 and 0 are both set to 0 to indicate that this PDU is an SMS-DELIVER
  614.     }
  615.     PDUType := StrToInt('$' + copy(FPDU, FSMSDeliverStartPos, 2));
  616.   except
  617.     PDUType := 0;
  618.     Form1.Debug('PDU ERROR (PDUType): '+Value);
  619.   end;
  620.   FIsSMSSumit := (PDUType and 3) = 1;
  621.   //Check there are Header Information
  622.   FIsUDH := (PDUType and 64) = 64;
  623.   // Get Validity Field Length
  624.   FValidityLen := 0;
  625.   Offset := 0;
  626.   if FIsSMSSumit then begin
  627.     TPVPF := (PDUType and $18) shr 3;
  628.  
  629.     case TPVPF of
  630.       1: FValidityLen := 14;
  631.       2: FValidityLen := 2;
  632.       3: FValidityLen := 14;
  633.     else FValidityLen := 0;
  634.     end;
  635.  
  636.     Offset := 2;
  637.   end;
  638.  
  639.   // Get Sender Field Length and Startpos
  640.   FSenderPos := FSMSDeliverStartPos + Offset + 4;
  641.   try
  642.     FSenderLen := StrToInt('$' + copy(FPDU, FSenderPos - 2, 2)); // count of sender's number digits
  643.   except
  644.     FSenderLen := 0;
  645.     Form1.Debug('PDU ERROR (SenderLen): '+Value);
  646.   end;
  647.   if (FSenderLen mod 2) > 0 then FSenderLen := FSenderLen + 1;
  648.  
  649.   FMessageRef := Copy(FPDU, FSMSDeliverStartPos + 2, 2);
  650.   try
  651.     {
  652.     The Type-of-Address octet indicates the format of a phone number. The most common value of this octet
  653.     is 91 hex (10010001 bin), which indicates international format. A phone number in international format
  654.     looks like 46708251358 (where the country code is 46). In the national (or unknown) format the same
  655.     phone number would look like 0708251358. The international format is the most generic, and it has to
  656.     be accepted also when the message is destined to a recipient in the same country as the MSC or as the SGSN.
  657.  
  658.     Using the unknown format (i.e. the Type-of-Address 81 instead of 91) would yield the phone number octet
  659.     sequence 7080523185 (0708251358). Note that this has the length 10 (A), which is even. 
  660.     }
  661.     TPDCS := StrToInt('$' + copy(FPDU, FSenderPos + FSenderLen + 4, 2));
  662.   except
  663.     TPDCS := 0;
  664.     Form1.Debug('PDU ERROR (TPDCS): '+Value);
  665.   end;
  666.   {
  667.     Should check DCS for $00abxxzz, where
  668.       a = compression flag
  669.       b = message class meaning
  670.      xx = message data coding
  671.      zz = message class
  672.  
  673.     So we are going to check bits 2 and 3 only ($00001100 = $C)
  674.   }
  675.   FDataCoding := (TPDCS and $0C) shr 2;
  676. end;
  677.  
  678. //*const
  679. //  Table: array[0..255] of byte =
  680. // ( // 0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
  681. //{0}   64, 163,  36, 165, 232, 233, 250, 236, 242, 199,  10, 216, 248,  13, 197, 229,
  682. //{1}    0,  95,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 198, 230, 223, 202,
  683. //{2}   32,  33,  34,  35, 164,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
  684. //{3}   48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
  685. //{4}  161,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
  686. //{5}   80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90, 196, 214, 209, 220, 167,
  687. //{6}  191,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
  688. //{7}  112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 228, 246, 241, 252, 224,
  689. //{8}    0,   0,   0,   0,   0,   0,   0,   0, 183,   0,   0,   0,   0,   0,   0,   0,
  690. //{9}    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
  691. //{A}    0, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172,   0, 174, 175,
  692. //{B}  176, 177, 178, 179, 180, 181, 182,   0, 184, 185, 186, 187, 188, 189, 190, 191,
  693. //{C}  192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
  694. //{D}  208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
  695. //{E}  224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
  696. //{E}  240, 241, 242, 243, 244, 245, 246,   0,   0, 249, 250, 251, 252, 253, 254, 255
  697. //  );
  698.  
  699. const
  700.   Table: array[0..255] of byte =
  701.   ( // 0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
  702. {0}   64, 163,  36, 165, 232, 233, 250, 236, 242, 199,  10, 216, 248,  13, 197, 229,
  703. {1}  196,  95, 214, 195, 203, 217, 208, 216, 211, 200, 206,   0, 198, 230, 223, 202,
  704. {2}   32,  33,  34,  35, 164,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
  705. {3}   48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
  706. {4}  161,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
  707. {5}   80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90, 196, 214, 209, 220, 167,
  708. {6}  191,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
  709. {7}  112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 228, 246, 241, 252, 224,
  710. {8}    0,   0,   0,   0,   0,   0,   0,   0, 183,   0,   0,   0,   0,   0,   0,   0,
  711. {9}    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
  712. {A}    0, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172,   0, 174, 175,
  713. {B}  176, 177, 178, 179, 180, 181, 182,   0, 184, 185, 186, 187, 188, 189, 190, 191,
  714. {C}  192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
  715. {D}  208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
  716. {E}  224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
  717. {E}  240, 241, 242, 243, 244, 245, 246,   0,   0, 249, 250, 251, 252, 253, 254, 255
  718.   );
  719.  
  720. var
  721.   GEscaped: Boolean;
  722.  
  723. function ConvertCharSet(inputChr: Char; toGSM: Boolean = False): Char;
  724. var
  725.   i: Integer;
  726.   found: Boolean;
  727. begin
  728.   Result := inputChr;
  729.  
  730.   if toGSM then begin
  731.     found := False;
  732.     for i := 0 to 255 do begin
  733.       if Table[i] = ord(inputChr) then begin
  734.         Result := chr(i);
  735.         found := True;
  736.         break;
  737.       end;
  738.     end;
  739.  
  740.     if not found then begin
  741.       case ord(inputChr) of
  742.       12: Result := chr(10);
  743.       91: Result := chr(60);
  744.       92: Result := chr(47);
  745.       93: Result := chr(62);
  746.       94: Result := chr(20);
  747.       123: Result := chr(40);
  748.       124: Result := chr(64);
  749.       125: Result := chr(41);
  750.       126: Result := chr(61);
  751.       164: Result := chr(101);
  752.       else
  753.         Result := chr(63);
  754.       end;
  755.     end;
  756.   end
  757.   else begin
  758.     if ord(inputChr) = $1B then begin
  759.       Result := chr(0);
  760.       GEscaped := True;
  761.     end
  762.     else begin
  763.       if GEscaped then begin
  764.         GEscaped := false;
  765.         case ord(inputChr) of
  766.           10: Result := chr(12);
  767.           20: Result := chr(94);
  768.           40: Result := chr(123);
  769.           41: Result := chr(125);
  770.           47: Result := chr(92);
  771.           60: Result := chr(91);
  772.           61: Result := chr(126);
  773.           62: Result := chr(93);
  774.           64: Result := chr(124);
  775.           101: Result := chr(164);
  776.         else
  777.           Result := chr(0);
  778.         end;
  779.       end
  780.       else Result := chr(Table[Ord(inputChr)]);
  781.     end;
  782.   end;
  783.  
  784. end;
  785.  
  786. function ConvertCharSet(inputStr: String; toGSM: Boolean): String;
  787. var
  788.   i, v: Integer;
  789.   escaped: Boolean;
  790. begin
  791.   Result := '';
  792.  
  793.   if toGSM then begin
  794.     for i := 0 to length(inputStr) do
  795.       Result := Result + ConvertCharSet(inputStr[i],toGSM);
  796.   end
  797.   else begin
  798.     escaped := false;
  799.     for i := 1 to length(inputStr) do begin
  800.       v := ord(inputStr[i]);
  801.  
  802.       if escaped then begin
  803.         escaped := false;
  804.         case v of
  805.           10: v := 12;
  806.           20: v := 94;
  807.           40: v := 123;
  808.           41: v := 125;
  809.           47: v := 92;
  810.           60: v := 91;
  811.           61: v := 126;
  812.           62: v := 93;
  813.           64: v := 124;
  814.          101: v := 164;
  815.         else
  816.           v := 0;
  817.         end;
  818.  
  819.         Result := Result + chr(v);
  820.       end
  821.       else begin
  822.         if v <> $1B then Result := Result + chr(Table[v])
  823.         else escaped := true;
  824.       end;
  825.     end;
  826.   end;
  827. end;
  828.  
  829.  
  830.  
  831. function TSMS.MakeCRLF(str: string): String;
  832. var
  833.   i: Integer;
  834.   skipnext: boolean;
  835. begin
  836.   Result := '';
  837.   skipnext := false;
  838.  
  839.   for i := 1 to length(str) do begin
  840.     if skipnext then skipnext := false
  841.     else begin
  842.       // check if already CRLF paired
  843.       if ((str[i] = #$0A) and (str[i+1] = #$0D)) or ((str[i] = #$0D) and (str[i+1] = #$0A)) then begin
  844.         Result := Result + #$0D + #$0A;
  845.         skipnext := true;
  846.       end
  847.       else if ((str[i] = #$0A) or (str[i] = #$0D)) then begin
  848.         Result := Result + #$0D + #$0A;
  849.       end
  850.       else begin
  851.         Result := Result + str[i];
  852.       end;
  853.     end;
  854.   end;
  855. end;
  856.  
  857. function CheckCodingType(str: WideString): Integer;
  858. var
  859.   str8bit: AnsiString;
  860.   i: Integer;
  861. begin
  862.   Result := 0;
  863.   str8bit := str;  
  864.   if str8bit <> str then
  865.     Result := 2
  866.   else begin
  867.     for i := 1 to length(str8bit) do begin
  868.       { workaround for UCS-2 excoding of cyrilic (and other i18n) chars }
  869.       if ForceUCSusage or (DoStrictUCScheck and (str8bit[i] in ['└'..'▀','α'..' '])) then begin
  870.         Result := 2;
  871.         break;
  872.       end;
  873.       if (ord(ConvertCharSet(str8bit[i], True)) and $80) = $80 then begin
  874.         Result := 1;
  875.         break;
  876.       end;
  877.     end;
  878.   end;
  879. end;
  880.  
  881. procedure TSMS.Set_MessageRef(const Value: String);
  882. begin
  883.   FMessageRef := Copy(Value,1,2);
  884.   while Length(FMessageRef) < 2 do FMessageRef := '0' + FMessageRef;
  885. end;
  886.  
  887. function TSMS.GetNewPDU(AMessageReference: String): String;
  888. begin
  889.   MessageReference := AMessageReference;
  890.  
  891.   FSMSCLen := StrToInt('$' + copy(FPDU, 1, 2)) * 2;
  892.   FSizeOfPDU := (Length(FPDU) - FSMSCLen) div 2 - 1;
  893.  
  894.   FSMSDeliverStartPos := 3;
  895.   if FSMSCLen > 0 then FSMSDeliverStartPos := FSMSDeliverStartPos + FSMSCLen;
  896.  
  897.   FPDU[FSMSDeliverStartPos + 2] := FMessageRef[1];
  898.   FPDU[FSMSDeliverStartPos + 3] := FMessageRef[2];
  899.  
  900.   Result := FPDU;
  901. end;
  902.  
  903. end.
  904.